/*
 * LogEx a Java library for extracting log file data
 * Copyright 2012 LogEx Team.
 *
 * This program is free software. You can distribute or modify it under the 
 * terms of the GNU Lesser General Public License version 3.0 as published by  
 * the Free Software Foundation.
 *
 * This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
 * INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, contact the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.logex.main;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.Map.Entry;
import javax.xml.xquery.XQException;
import org.logex.obj.LinePattern;
import org.logex.obj.Sibling;

/**
 * Executes the data extraction task from ASCII based log files. Uses the
 * corresponding <code>RootObject</code> generated from the SDL script to
 * determine the structure of the log file, before extracting data according to
 * it.
 * 
 * @author Lohitha Chiranjeewa
 */
public class AsciiParser {

	/**
	 * @param args
	 */
	private String xmlFile;
	private String temp;
	private String secondaryTag;
	private String streamLine;
	private String fullLogLine;
	private String delimiter;
	private String fixedVal;
	private String startsNotReq;
	private String endsNotReq;
	private boolean required;
	private boolean siblingMode;
	private RootObject rootObject;
	private Map<String, String> xmlMap;
	private List<Sibling> xmlMapList;
	private List<Object> siblingPatterns;
	private XmlWriter xmlWriter;

	/**
	 * Constructor for <code>ParserReader</code> class. Creates name of XML file
	 * to map with log file name.
	 * 
	 * @param logFileName
	 *            Name of the log file
	 */
	public AsciiParser(String logFileName) {
		if (logFileName.contains(".")) {
			xmlFile = logFileName.substring(0, logFileName.lastIndexOf("."))
					+ StringConstants.XML_EXTENSION;
		} else {
			xmlFile = logFileName + StringConstants.XML_EXTENSION;
		}
		xmlWriter = new XmlWriter(this);
	}

	/**
	 * Writes the remaining Log Entries to the database in case log file lines
	 * runs out before reaching Log Entry limit.
	 * 
	 * @param line
	 *            The line read from the log file
	 * @param currentLine
	 *            Line number of the currently reading line
	 * @throws IOException
	 *             Due to error writing to xml file
	 * @throws XQException
	 *             If reasons such as wrong commands, DB errors occur
	 */
	public void writeRemainingLogEntries(String line, int currentLine)
			throws IOException, XQException {
		streamLine = line;
		if (currentLine != 0) {
			if (siblingMode) {
				xmlWriter.bundleWithSiblings(xmlMapList, xmlFile);
			} else {
				xmlWriter.writeBundleToDb(xmlFile);
			}
		}
	}

	/**
	 * Initializes the lists <code>xmlMapList</code> and
	 * <code>siblingPatterns</code>. Writes initial elements into corresponding
	 * XML file.
	 * 
	 * @throws IOException
	 *             Due to errors in writing to XML file
	 * @throws XQException
	 *             If reasons such as wrong commands, DB errors occur
	 * */
	public void prepareLists() throws IOException, XQException {
		xmlMapList = new ArrayList<Sibling>();
		siblingPatterns = new ArrayList<Object>();
		xmlWriter.writeOutermostElements(xmlFile);
	}

	/**
	 * Processes the String <code>line</code> according to the description in
	 * the SDL file and stores the extracted data tokens in the database.
	 * 
	 * @param line
	 *            The line to be processed
	 * @throws IOExceptions
	 *             Due to errors in writing to / reading from file
	 * @throws XQException
	 *             If reasons such as wrong commands, DB errors occur
	 * 
	 */
	public void actOnLine(String line) throws IOException, XQException {
		streamLine = line;
		fullLogLine = line;
		while (true) {
			xmlMap = new LinkedHashMap<String, String>();
			streamLine = streamLine.replaceAll("&", "&amp;");
			streamLine = streamLine.replaceAll("<", "&lt;");
			streamLine = streamLine.replaceAll(">", "&gt;");
			temp = streamLine;
			for (Entry<String, LinePattern> valuePairs : rootObject
					.getMainMap().entrySet()) {
				secondaryTag = valuePairs.getKey();
				streamLine = temp;
				if (((siblingMode) && (siblingPatterns.contains(secondaryTag)))
						|| (!siblingMode)) {
					for (int i = 0; i < valuePairs.getValue().getMainList()
							.size(); i++) {
						if (!parseLine(valuePairs.getValue().getMainList(), i)) {
							break;
						}
					}
				}
				if (!xmlMap.isEmpty()) {
					if (valuePairs.getValue().getSiblingPatterns().size() != 0) {
						siblingMode = true;
						siblingPatterns.addAll(valuePairs.getValue()
								.getSiblingPatterns());
					}
					break;
				}
			}
			if (!siblingMode) {
				xmlWriter.bundleWithoutSiblings(secondaryTag, xmlMap, xmlFile);
				xmlMap.clear();
				break;
			} else if ((siblingMode) && (!xmlMap.isEmpty())) {
				xmlMapList.add(new Sibling(secondaryTag, xmlMap));
				break;
			} else if ((siblingMode) && (xmlMap.isEmpty())) {
				siblingMode = false;
				xmlWriter.bundleWithSiblings(xmlMapList, xmlFile);
				xmlMapList.clear();
				siblingPatterns.clear();
				xmlMap.clear();
			}
		}
	}

	/**
	 * Compares given log file line with all available patterns.
	 * 
	 * @param mainList
	 *            ArrayList containing log file line patterns
	 * @param position
	 *            Which positions of ArrayList to check
	 * @return Boolean value on whether the parsing was a success or not
	 */
	private boolean parseLine(ArrayList<SortedMap<String, Object>> mainList,
			int position) {
		fixedVal = null;
		startsNotReq = null;
		endsNotReq = null;
		delimiter = StringConstants.getDelimiterChar((String) mainList.get(
				position).get(StringConstants.DELIMITER));
		required = (Boolean) mainList.get(position).get(
				StringConstants.REQUIRED);
		if (mainList.get(position).get(StringConstants.FIXED_VAL) != null) {
			fixedVal = (String) mainList.get(position).get(
					StringConstants.FIXED_VAL);
		}
		if (mainList.get(position).get(StringConstants.STARTS_NOT_REQ) != null) {
			startsNotReq = StringConstants.getEdgeChar((String) mainList.get(
					position).get(StringConstants.STARTS_NOT_REQ));
			if (!streamLine.startsWith(startsNotReq)) {
				xmlMap.clear();
				return false;
			}
		}
		if (mainList.get(position).get(StringConstants.ENDS_NOT_REQ) != null) {
			endsNotReq = StringConstants.getEdgeChar((String) mainList.get(
					position).get(StringConstants.ENDS_NOT_REQ));
		}
		int index = determineIndex();
		if (index == -1) {
			xmlMap.clear();
			return false;
		}
		String sub = trimString(index);
		if (fixedVal != null && !fixedVal.equals(sub)) {
			xmlMap.clear();
			return false;
		}
		if (required) {
			xmlMap.put(
					(String) mainList.get(position).get(StringConstants.TITLE),
					sub);
		}
		if (mainList.get(position).get(StringConstants.ENDS_NOT_REQ) != null) {
			streamLine = streamLine.substring(index + endsNotReq.length());
		} else if (index < streamLine.length()) {
			streamLine = streamLine.substring(index + 1);
		}
		while (streamLine.startsWith(delimiter)) {
			streamLine = streamLine.substring(delimiter.length());
		}
		while ((streamLine.startsWith(" ")) || (streamLine.startsWith("\t"))) {
			streamLine = streamLine.substring(1);
		}
		return true;
	}

	/**
	 * Returns part of incoming String according to splitting rules.
	 * 
	 * @param index
	 *            The index from which the String should be separated
	 * @param startsNotReq
	 *            The character(s) from which the String starts but should be
	 *            discarded
	 * @param streamLine
	 *            Original String
	 * @return Trimmed String
	 */
	public String trimString(int index) {
		if ((index != -1)
				&& ((startsNotReq != null) && (streamLine
						.startsWith(startsNotReq))))
			return streamLine.substring(startsNotReq.length(), index).trim();
		else if (index != -1)
			return streamLine.substring(0, index).trim();
		else
			return streamLine.trim();
	}

	/**
	 * Determines the index of the String from which it should be separated.
	 * 
	 * @param streamLine
	 *            Original String
	 * @param endsNotReq
	 *            Ending character(s) of the String which should be discarded
	 * @param delimiter
	 *            Delimiting character of the element
	 * @return The index
	 */
	public int determineIndex() {
		if (!streamLine.endsWith("\n") && delimiter.equals("\n")) {
			streamLine = streamLine + "\n";
		}
		if (endsNotReq != null) {
			return streamLine.indexOf(endsNotReq + delimiter);
		} else {
			return streamLine.indexOf(delimiter);
		}
	}

	/**
	 * @param delimiter
	 *            the delimiter to set
	 */
	public void setDelimiter(String delimiter) {
		this.delimiter = delimiter;
	}

	/**
	 * @param startsNotReq
	 *            the startsNotReq to set
	 */
	public void setStartsNotReq(String startsNotReq) {
		this.startsNotReq = startsNotReq;
	}

	/**
	 * @param endsNotReq
	 *            the endsNotReq to set
	 */
	public void setEndsNotReq(String endsNotReq) {
		this.endsNotReq = endsNotReq;
	}

	/**
	 * @param streamLine
	 *            the streamLine to set
	 */
	public void setStreamLine(String streamLine) {
		this.streamLine = streamLine;
	}

	/**
	 * @return the fullLogLine
	 */
	public String getFullLogLine() {
		return fullLogLine;
	}

	/**
	 * @return the streamLine
	 */
	public String getStreamLine() {
		return streamLine;
	}

	/**
	 * @param rootObject
	 *            Set the rootObject which gained parsing the sdl file.
	 * 
	 * */
	public void setRootObject(RootObject rootObject) {
		this.rootObject = rootObject;
	}
}
